#include <jni.h>
#include <sys/stat.h>
#include <Android/log.h>
#include <malloc.h>
#include <string.h>

#define TAG "AndrewLi"

#define LOGD(...)  (__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
#define LOGI(...)  (__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGW(...)  (__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
#define LOGE(...)  (__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
#define LOGF(...)  (__android_log_print(ANDROID_LOG_FATAL, TAG, __VA_ARGS__))

static const char * RELEASE_SIGN = "308202c5308201ada00302010202040997ab3f300d06092a864886f70d01010b050030133111300f06035504031308616e647265776c69301e170d3137303130353032313634315a170d3431313233303032313634315a30133111300f06035504031308616e647265776c6930820122300d06092a864886f70d01010105000382010f003082010a02820101008380b84f2f9aa29afa9aced9bb309816b625e38f40e095253dc607ef456ae3ef98f2b371dbd2162442b6cd16b1e5813a0cc632c5d4e6c87f87b50e0b35f4e023f8cdf244643bb81cdbf0d4667be5d05811f164fcd02c458df0d32c764364bc6d9fe717ccfc7e81e96cf63fd13ee6d382039964b5ce67fd73d2b4aef5fd862cdb816db22a965004c9817e477c2b14b1b2aa7fbd5e6afb52f87d10d54b370a11060b963df5eebf5928f6b8af1d004c1261db72dd23edd52afdcf9ce71fe0938a6590911dffecc3cf2603aa1b093b942dbe331c171a70bc981d536d60a2971aa166d1c49f176e426e0c809ffbd3bfc5d85fb9943f4b69a332af4e094ca7c43cf2ad0203010001a321301f301d0603551d0e04160414b3180b167c8a654eac6d33b496b2cbfc05455fed300d06092a864886f70d01010b050003820101002d692e7908acffbaab055971915aa979f7d8c8ece8ce10d3dba47d7d4d82cd02e933d576e750a06137bf0ba215752037fc730ef8222e8d24a045216bbc320608b79500e56dc0499b274c205a358ebae13112cfeb05607adbe5b3c0d9e99347c80b3a38812440599e6f8e5ea6b3891ce842d9e7986fb9ea799a46de64f4f749271aba055cc215a72b30ae4bb24bda1fbf9a1ceb356501f91104c47284b972d873c833e8ab542b8101e3788722f0184e631bd24a04a5219ccb92e2658b357d1ef0ca56a5ad1ce6439c3ccc5a6cd81c457664aeb8fb101b2a74bb9c0afa1f12b17e86233c54d33324500a63dd5cb558eae81b11ce4e5c55474a652a5573b2f04ef6";
static const int GET_PACKAGE_INFO_FLAG_GET_RESOLVED_FILTER = 0x00000040;

jobject getApplication(JNIEnv *env) {
    jclass mainThreadClass = env->FindClass("android/app/ActivityThread");
    if(NULL != mainThreadClass) {
        jmethodID methodIDCurApp = env->GetStaticMethodID(mainThreadClass, "currentApplication", "()Landroid/app/Application;");
        if(NULL != methodIDCurApp) {
            jobject application = env->CallStaticObjectMethod(mainThreadClass, methodIDCurApp);
            return application;
        }
        return NULL;
    }
}

char* jstringToCString(JNIEnv *env, jstring jstr) {
    char* str = NULL;
    jclass stringClass = env->FindClass("java/lang/String");
    jstring strEncode = env->NewStringUTF("utf-8");
    jmethodID methodIDGetBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray stringByteArr = (jbyteArray)env->CallObjectMethod(jstr, methodIDGetBytes, strEncode);
    jsize len = env->GetArrayLength(stringByteArr);
    jbyte * ba = env->GetByteArrayElements(stringByteArr, JNI_FALSE);
    if(len > 0) {
        str = (char*)malloc(len + 1);
        memcpy(str,ba,len);
        str[len] = 0;
    }
    env->ReleaseByteArrayElements(stringByteArr, ba, 0);
    return str;
}

jboolean verifySign(JNIEnv *env) {
    jobject app = getApplication(env);

    jclass appClass = env->GetObjectClass(app);
    jmethodID  methodIDGetPM = env->GetMethodID(appClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject packageManager = env->CallObjectMethod(app, methodIDGetPM);

    jclass packageManagerClass = env->GetObjectClass(packageManager);
    jmethodID methodIDGetPackName = env->GetMethodID(appClass, "getPackageName", "()Ljava/lang/String;");
    jstring strPackName = static_cast<jstring>(env->CallObjectMethod(app, methodIDGetPackName));

    jmethodID methodIDGetPackInfo = env->GetMethodID(packageManagerClass, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jobject packInfo = env->CallObjectMethod(packageManager, methodIDGetPackInfo, strPackName, GET_PACKAGE_INFO_FLAG_GET_RESOLVED_FILTER);

    jclass packInfoClass = env->GetObjectClass(packInfo);
    jfieldID fieldIDSignatures = env->GetFieldID(packInfoClass, "signatures", "[Landroid/content/pm/Signature;");

    jobject signature = env->GetObjectField(packInfo, fieldIDSignatures);
    jobjectArray signatures = reinterpret_cast<jobjectArray>(signature);

    jobject sign = env->GetObjectArrayElement(signatures,0);
    jclass signatureClass = env->GetObjectClass(sign);
    jmethodID methodIDToCharString = env->GetMethodID(signatureClass,"toCharsString", "()Ljava/lang/String;");

    jstring strSign = static_cast<jstring>(env->CallObjectMethod(sign, methodIDToCharString));
    char* str = jstringToCString(env, strSign);
    //LOGD("the signature is : %s", str);
    if(strcmp(RELEASE_SIGN, str) != 0) {
        free(str);
        LOGE("verify release signature fail!");
        return false;
    }
    else {
        free(str);
        LOGD("verify release signature success!");
        return true;
    }
}

jstring getSecret(JNIEnv *env, jobject obj) {
    //LOGD("Enter getSecret.");
    if(!verifySign(env)) {
        return env->NewStringUTF("Wrong signature! You cannot get my secret.");
    }
    return env->NewStringUTF("This is my secret!");
}

static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods, int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if(clazz == NULL) {
        return JNI_FALSE;
    }
    if(env->RegisterNatives(clazz,gMethods,numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static JNINativeMethod gMethods[] = {
        {"getSecret", "()Ljava/lang/String;",(jstring*)getSecret },
};

static int registerNatives(JNIEnv *env) {
    const char *kClassName = "com/andrewli/demo/getsecretbyjnidemo/JniUtils";
    return registerNativeMethods(env, kClassName, gMethods, sizeof(gMethods)/sizeof(gMethods[0]));
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;

    //LOGI("in jni OnLoad");
    if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }

    if(!registerNatives(env)) {
        LOGE("register native methods fail");
        return -1;
    }

    result = JNI_VERSION_1_4;
    //LOGI("register native methods success");
    return result;
}


